﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Diagnostics;
using ProcessPrivileges;
using System.Threading;
using System.Text.RegularExpressions;

namespace WindowsFormsApplication1
{
    public partial class Copyfiles : Form
    {
        public Copyfiles()
        {
            InitializeComponent();
        }



       
        public class Logger
        {
            string logName = @"c:\FileCopy.txt"; //where this program writes to a log
            string ErrorlogName = @"c:\FileCopyErrors.txt"; //where this program writes to a log


            public void ClearCurrentLogs()
            {
                //if the log file exists, delete it so we can start fresh
                if (File.Exists(logName))
                {
                    File.Delete(logName);
                }

                //if the log file exists, delete it so we can start fresh
                if (File.Exists(ErrorlogName))
                {
                    File.Delete(ErrorlogName);
                }
            }

            public void ErrorLog(string message)
            {

                //Create Error log for easier reading
                // TextWriter twError = new StreamWriter(ErrorlogName, true);

                using (StreamWriter w = new StreamWriter(ErrorlogName, true))
                {
                    w.WriteLine("[{0}][{1}] {2} ", String.Format("{0:yyyyMMdd HH:mm:ss}", DateTime.Now), "Info", message);
                    w.Flush();
                }
            }


            public void Log(string message)
            {


                //create the log file
                //TextWriter tw = new StreamWriter(logName, true);

                using (StreamWriter w = new StreamWriter(logName, true))
                {
                    w.WriteLine("[{0}][{1}] {2} ", String.Format("{0:yyyyMMdd HH:mm:ss}", DateTime.Now), "Info", message);
                    w.Flush();
                }
            }

        }


        static Logger logger = new Logger();

        DBConnection dbConns = new DBConnection();


        private void button1_Click(object sender, EventArgs e)
        {

           

            //this.backgroundWorker1.RunWorkerAsync();

            //they can press the copy button but cannot see the DONE! label
            this.button1.Enabled = true;
            this.lblFunny.Visible = false;

            string startDir = this.lblStart.Text + @"\"; //holds the user entered start directory
            string DestFilePathBase = this.lblDone.Text + @"\";//holds the user entered copy to directory
            int FolderCount; 
            string FileOwnerEntered = txtUserName.Text;


            logger.ClearCurrentLogs();



            //check user inputs... definitely a better way to do this but this was quickest for me
            if (FileOwnerEntered == string.Empty || FileOwnerEntered == null)
            {
                MessageBox.Show("Please enter a value for UserName"); 
                return;
            }

            if (lblStart.Text == string.Empty || lblStart.Text == null)
            {
                MessageBox.Show("Please enter location to start from");
                return;
            }

            if (lblDone.Text == string.Empty || lblDone.Text == null)
            {
                MessageBox.Show("Please enter location to copy to");
                return;
            }


            //reset the progress bar if they click the big button again (do prior to getting a list of folders which can take a bit)
            progressBar1.Value = 0;

            //get a list of all directories
            //string[] directories;
            //var directories = Directory.GetDirectories(startDir, "*", System.IO.SearchOption.AllDirectories).Where(s => !s.EndsWith("System Volume Information"));
            List<string> directories = new List<string>();
            //This will load up our SQL table with all necessary directories
            if (!this.chkUseFolderList.Checked)
                GetDirectoriesAndSubs(startDir, true);//, directories);

            if (this.chkJustGetFolders.Checked)
            {
                progressBar1.Maximum = 2;
                progressBar1.Value = 1;
                progressBar1.Step = 1;
                progressBar1.PerformStep();
                //at the end of the loop show the DONE! and close out our log writer
                this.button1.Enabled = true;
                this.lblFunny.Visible = true;
                return;

            }
                //IEnumerable<string> directories = ShowAllFoldersUnder(startDir);

                //get the overall file count, so we can do a progress bar
                //get count from sql
                FolderCount = dbConns.GetCount("SELECT COUNT(1) as CountValue from dbo.UserFolderDirectory");

            //set progress bar up to step up one file at a time
            progressBar1.Maximum = FolderCount;
            progressBar1.Value = 0;
            progressBar1.Step = 1;

            //Since moving to new folder and subfolder process we return the root folder
            //check the root directory for files
            //ProcessFiles(startDir, DestFilePathBase);

            //now go through every directory one by one
            //change to loop thru the sql table and just get one directory at a time
            //foreach (string directory in directories)
            string directory = startDir;
            for (int i = 1; i <= FolderCount; i++)
            {
                

                // Process each directory recursively
                //ProcessFiles(directory);
                logger.Log("Started Directory: " + directory);
                //tw.WriteLine("Started Directory: " + directory);

              
                 ProcessFiles(directory, DestFilePathBase);


                directory = dbConns.GetNextDirectory(directory);

                //increment the progress bar
                progressBar1.PerformStep(); 

            }

            //at the end of the loop show the DONE! 
            this.button1.Enabled = true;
            this.lblFunny.Visible = true;
            

        }



        //pass my start folder:
        //add it to the list
        //get the list of all folders under that directory
        //start at the top
        //get a list of all directories under those folders



        //private void ShowAllFoldersUnder(string path)//, int indent)
        //{

        //    try
        //    {
        //        if ((File.GetAttributes(path) & FileAttributes.ReparsePoint)
        //            != FileAttributes.ReparsePoint)
        //        {
        //            foreach (string folder in Directory.GetDirectories(path))
        //            {
        //                directories.Add(Path.GetFileName(folder));
        //                //Console.WriteLine(
        //                //    "{0}{1}", new string(' ', indent), Path.GetFileName(folder));
        //                ShowAllFoldersUnder(folder);//, indent + 2);
        //            }
        //        }
        //    }
        //    catch (UnauthorizedAccessException)
        //    {
        //        logger.ErrorLog("File access denied: " + Path.GetFileName(path));

        //        //twError.WriteLine("File access denied: " + Path.GetFileName(folder));
        //    }

        //    return directories;
        //}



        private void GetDirectoriesAndSubs(string path, Boolean IsRoot)
        {
            List<string> _dirs = new List<string>();

            //string[] _dirs = null;
            //_dirs. = Directory.GetDirectories(path);

            //List<string> _dirs = new List<string>();

            //add the start folder
            if (IsRoot)
            {
                dbConns.RunTrucnate("UserFolderDirectory");
                dbConns.RunTrucnate("UserFolderDirectoryError");

                //Dirs.Add(path);
                dbConns.RunInsert(path);
            }
            //try catch to capture access denied errors and log em
            try
            {
                _dirs.AddRange(Directory.GetDirectories(path));

            }
            catch (Exception ex)
            {
                dbConns.RunErrorInsert(path);

                logger.ErrorLog(ex.ToString() + Path.GetFileName(path));
            }

            try
            {
                foreach (string folder in Directory.GetDirectories(path))
                {
                    dbConns.RunInsert(folder);
                    GetDirectoriesAndSubs(folder, false);
                }
            }
            catch (Exception ex)
            {
                dbConns.RunErrorInsert(Path.GetFileName(path));
                logger.ErrorLog(ex.ToString() + Path.GetFileName(path));
            }

        }

      


        private void ProcessFiles(string startDir, string DestFilePathBase)
        {
            int SuccessCopy = 0;
            string DestFilePath;//just get the path part of where to put the copy to, so as to create it as necessary (it being the folder)
            string DestFileFolder;
            string FileOwner;
            string FileOwnerEntered = txtUserName.Text;
            string SourceFileBase = this.lblStart.Text + @"\";
            string ErrorFolder;//= DestFilePathBase + @"Errors\";

            //get the list of files in the selected directory
            //string[] files = Directory.GetFiles(startDir, "*.*", SearchOption.AllDirectories);
            string[] files;
            string FileOwnerRaw = txtUserName.Text;
            string[] FileOwnerLoop = FileOwnerRaw.Split(',').Select(p => p.Trim()).ToArray();

            try
            {
                files = Directory.GetFiles(startDir);
            }
            catch(Exception ex)
            {
                //tw.WriteLine("{0} Exception caught. File Path too long.", ex);
                //string err = new string("{0} Exception caught. File Path too long.", ex);
                logger.Log(ex.ToString() + " Exception caught. File Path too long.");

                //twError.WriteLine("File path too long: " + startDir);
                logger.ErrorLog("File path too long: " + startDir);

                files = new string[] { };
            }

            // DestFileFolder = startDir.Replace(SourceFileBase, DestFilePathBase);

            // For every file in the copy from directory lets go see what we need to copy, copy it
            if (files.Count() > 0)
            {
                foreach (string file in files)
                {
                    SuccessCopy = 0;

                    //log file line to say I started this file
                    //tw.WriteLine("Started: " + file);

                    logger.Log("Started: " + file);


                    //check length because somehow that becomes a problem in very rare cases
                    if (file.Length < 260) // && DestFileFolder.Length <= 248)
                    {

                        //somehow a document can lose its owner and this will fail in that case
                        try
                        {
                            FileOwner = GetFileOwner(file); //get the file owner
                        }
                        catch (Exception ex)
                        {
                            FileOwner = "SKIP";

                            logger.Log(ex.ToString() + "Exception caught. Cannot get owner");

                            //tw.WriteLine("{0} Exception caught. Cannot get owner", ex);
                            //twError.WriteLine("Cannot get owner: " + file);
                            logger.ErrorLog("Cannot get owner: " + file);

                        }

                        //grand idea to store data on folders.. but the files is what matters so bleh.
                        //dbConns.SetOwner(startDir, FileOwner);

                        //finally, check the owner against the one we were passed to see if we need to do anything with the file
                        //loop through each owner we were given and see if the file is owned by one of them
                        foreach (string FileOwnerVal in FileOwnerLoop)
                        {
                            ErrorFolder = GetDestFilePathBase(FileOwner, true);

                            if (FileOwner.ToUpper() == FileOwnerVal.ToUpper()) //check it against what the user has entered to look for
                            {
                                DestFilePath = GetDestinationFolder(file, FileOwner.ToUpper());

                                if (DestFilePath != "")
                                {
                                    DestFileFolder = Path.GetDirectoryName(DestFilePath);


                                    //only create the directory if we need to copy a file to it
                                    try
                                    {
                                        System.IO.Directory.CreateDirectory(DestFileFolder); //if the folder exists that is ok
                                    }
                                    catch (Exception ex)
                                    {
                                        DestFileFolder = GetDestFilePathBase(FileOwner, true);
                                        //tw.WriteLine("{0} Exception caught. Cannot create directory", ex);

                                        logger.Log(ex.ToString() + "Exception caught. Cannot create directory");

                                        //twError.WriteLine("Cannot create directory: " + file);
                                        logger.ErrorLog("Cannot create directory: " + file);

                                    }

                                    //if the dest file path is going to be too long, lets just move it to the error directory
                                    if (DestFilePath.Length > 250)
                                    {
                                        //create an "error" directory

                                        System.IO.Directory.CreateDirectory(ErrorFolder);
                                        DestFilePath = ErrorFolder + Path.GetFileName(DestFilePath);
                                    }
                                    //MessageBox.Show(DestFilePath.ToString());
                                    try
                                    {
                                        CopyAndSetFileOwner(file, DestFilePath, FileOwner); //copy the file, keeping the original owner
                                        SuccessCopy = 1;
                                    }
                                    catch (Exception ex)
                                    {
                                        SuccessCopy = 0;
                                        //tw.WriteLine("{0} Exception caught. Cannot Copy", ex);

                                        logger.Log(ex.ToString() + "Exception caught. Cannot Copy.");

                                        //twError.WriteLine("Cannot copy: " + file);
                                        logger.ErrorLog("Cannot copy: " + file);
                                    }

                                }

                            }
                            else
                            {
                                SuccessCopy = 2;
                            }
                        }

                        //write out a success or fail line to the log
                        if (SuccessCopy == 0)
                        {
                            //tw.WriteLine("Failed: " + file);
                            logger.Log("Failed: " + file);

                        }
                        else if (SuccessCopy == 1)
                        {
                            //tw.WriteLine("Success: " + file);
                            logger.Log("Success: " + file);

                        }
                        else if (SuccessCopy == 2)
                        {
                            //tw.WriteLine("Skipped: " + file);
                            logger.Log("Skipped: " + file);

                        }
                    }
                    else
                    {
                        //tw.WriteLine("Too Long: " + file); 
                         logger.Log("Too Long: " + file);
                    }
                }
            }
        }


        //public static IEnumerable<string> GetFilesByHand(string root)//, string searchPattern)
        //{
        //    Stack<string> pending = new Stack<string>();
        //    pending.Push(root);
        //    while (pending.Count != 0)
        //    {
        //        var path = pending.Pop();
        //        string[] next = null;
        //        try
        //        {
        //            next = Directory.GetFiles(path);//, searchPattern);
        //        }
        //        catch { }
        //        if (next != null && next.Length != 0)
        //            foreach (var file in next) yield return file;
        //        //try
        //        //{
        //        //    next = Directory.GetDirectories(path);
        //        //    foreach (var subdir in next) pending.Push(subdir);
        //        //}
        //        //catch { }
        //    }
        //}

        private string GetDestFilePathBase(string OwnerName, bool IsErrorFolder)
        {
            string DestFilePathBase;
            string domainUser = Regex.Replace(OwnerName, ".*\\\\(.*)", "$1", RegexOptions.None);

            DestFilePathBase = this.lblDone.Text + @"\" + domainUser + @"\";


            if (IsErrorFolder)
            {
                DestFilePathBase += @"Errors\";
            }

            return DestFilePathBase;
        }

        private string GetDestinationFolder(string SourceFileBase, string OwnerName)
        {
            string DestFilePathBase;
            string DestFilePath;
            string DestFileFolder;
            string SourceStartFolder = this.lblStart.Text + @"\";
            //string domainUser = Regex.Replace(OwnerName, ".*\\\\(.*)", "$1", RegexOptions.None);

            //DestFilePathBase = this.lblDone.Text + @"\" + domainUser + @"\";

            DestFilePathBase = GetDestFilePathBase(OwnerName, false);

            //get the destination path so we can create the folder structure but starting with the new location
            DestFilePath = SourceFileBase.Replace(SourceStartFolder, DestFilePathBase);
            
            //DestFilePath += Path.GetFileName(SourceFileBase);
             

            try
            {
                DestFileFolder = Path.GetDirectoryName(DestFilePath);
            }
            catch (Exception ex)
            {
                DestFileFolder = DestFilePathBase + @"Errors\";
                //tw.WriteLine("{0} Exception caught. Review Error Log", ex);

                logger.Log(ex.ToString()+ "Exception caught. Review Error Log");

                //twError.WriteLine("Destination File Path Too Long: " + SourceFileBase);
                logger.ErrorLog("Destination File Path Too Long: " + SourceFileBase);
            }

            if (DestFileFolder.Length > 248)
            {
                DestFilePath = "";
            }

            if (DestFilePath.Length > 260)
            {
                DestFilePath = "";
            }

            //DestFileFolder = Path.GetDirectoryName(DestFilePath);

            return DestFilePath;

        }

        private string GetFileOwner(string CurrentFilePath)
        {
             

            var fs = File.GetAccessControl(CurrentFilePath);

            var sid = fs.GetOwner(typeof(SecurityIdentifier));
            //Console.WriteLine(sid); // SID

            var ntAccount = sid.Translate(typeof(NTAccount));
            //Console.WriteLine(ntAccount); // DOMAIN\username

            return ntAccount.ToString();
        }


        private void CopyAndSetFileOwner(string originFile, string destinationFile, string FileOwner)
        {
            //set owner
            Process process = Process.GetCurrentProcess();
            NTAccount ntUser = new NTAccount(FileOwner);

            using (new PrivilegeEnabler(process, Privilege.Security, Privilege.TakeOwnership, Privilege.Restore))
            {
                //this *should* move over all the file attributes from the copied file but it does not bring over the owner
                try
                {
                   

                    File.Copy(originFile, destinationFile, true);
                    FileInfo originFileInfo = new FileInfo(originFile);
                    FileInfo destinationFileInfo = new FileInfo(destinationFile);
                    FileSecurity ac1 = originFileInfo.GetAccessControl(AccessControlSections.All);
                    ac1.SetAccessRuleProtection(true, true);                     
                    destinationFileInfo.SetAccessControl(ac1);                  

                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());

                }
            }

            //restore privelege lets you assign a user other than yourself as the owner of a file
            using (new PrivilegeEnabler(process, Privilege.Restore))
            {
                // Privilege is enabled within the using block. 

                try
                {

                    var fileSecurity = new FileSecurity();
                    fileSecurity.SetOwner(ntUser);
                    File.SetAccessControl(destinationFile, fileSecurity);
                    
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.ToString());

                }
            }

            using (new PrivilegeEnabler(process, Privilege.Security, Privilege.TakeOwnership, Privilege.Restore))
            {
                FileInfo destinationFileInfo2 = new FileInfo(destinationFile);
                var sid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
                var Security = destinationFileInfo2.GetAccessControl(AccessControlSections.Access);

                Security.AddAccessRule(
                   new FileSystemAccessRule(
                       sid,
                       FileSystemRights.Read,
                       //InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                       //PropagationFlags.None,
                       AccessControlType.Allow));

                File.SetAccessControl(destinationFile, Security);
            }
        }

        private void Copyfiles_Load(object sender, EventArgs e)
        {

            this.backgroundWorker1.RunWorkerAsync();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            //Get the directory to copy from
            DialogResult result = this.folderBrowserDialog1.ShowDialog();
            if (result == DialogResult.OK)
            {
                
                lblStart.Text = folderBrowserDialog1.SelectedPath;
                lblStart.Visible = true;

            }
        }

        private void btnMove_Click(object sender, EventArgs e)
        {
            //get the directory to copy to
            DialogResult result2 = this.folderBrowserDialog2.ShowDialog();
            if (result2 == DialogResult.OK)
            {

                this.lblDone.Text = folderBrowserDialog2.SelectedPath;
                lblDone.Visible = true;
                this.button1.Visible = true;
                this.progressBar1.Visible = true;

            }
        }

        private void lblUserName_Click(object sender, EventArgs e)
        {

        }

        private void txtUserName_TextChanged(object sender, EventArgs e)
        {

        }
    }
}
